﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Plugins;
using Xenta.Security;
using Xenta.Structs;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a message.
    /// </summary>
    public class CreateMessage : CreateAuditable<MessageEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("MSG");
            }
        }

        /// <summary>
        /// Gets or sets the sender unique identifier.
        /// </summary>
        public Guid SenderID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sender address.
        /// </summary>
        public string From
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient address.
        /// </summary>
        public string To
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the BCC recipient address.
        /// </summary>
        public string BccTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the address where to reply to.
        /// </summary>
        public string ReplyTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the subject.
        /// </summary>
        public string Subject
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        public string Text
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the number of send attempts.
        /// </summary>
        public int SendAttemptCount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public MessageFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(String.IsNullOrWhiteSpace(From))
                return Err.Of<MessageEntity>().NotValid("From");
            if(String.IsNullOrWhiteSpace(To))
                return Err.Of<MessageEntity>().NotValid("To");
            if(!uow.Infrastructure.CheckDependency<IMessageSender>(SenderID))
                return Err.Of<IMessageSender>().NotFound();
            return base.Validate(uow);
        }

        protected override void Construct(IUnitOfWork uow, MessageEntity entity)
        {
            base.Construct(uow, entity);

            entity.SenderID = SenderID;
            entity.From = From.TrimToLen(100);
            entity.To = To.TrimToLen(100);
            entity.BccTo = StringHelper.EnsureNotNull(BccTo).TrimToLen(100);
            entity.ReplyTo = StringHelper.EnsureNotNull(ReplyTo).TrimToLen(100);
            entity.Subject = StringHelper.EnsureNotNull(Subject).TrimToLen(100);
            entity.Text = StringHelper.EnsureNotNull(Text);
            entity.SendAttemptCount = SendAttemptCount;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the message.
    /// </summary>
    public class UpdateMessage : UpdateAuditable<MessageEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("MSG");
            }
        }

        /// <summary>
        /// Gets or sets the sender address.
        /// </summary>
        public string From
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient address.
        /// </summary>
        public string To
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the BCC recipient address.
        /// </summary>
        public string BccTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the address where to reply to.
        /// </summary>
        public string ReplyTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the subject.
        /// </summary>
        public string Subject
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        public string Text
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the number of send attempts.
        /// </summary>
        public int SendAttemptCount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public MessageFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(String.IsNullOrWhiteSpace(From))
                return Err.Of<MessageEntity>().NotValid("From");
            if(String.IsNullOrWhiteSpace(To))
                return Err.Of<MessageEntity>().NotValid("To");
            return base.Validate(uow);
        }

        protected override void Revise(IUnitOfWork uow, MessageEntity entity)
        {
            base.Revise(uow, entity);

            entity.From = From.TrimToLen(100);
            entity.To = To.TrimToLen(100);
            entity.BccTo = StringHelper.EnsureNotNull(BccTo).TrimToLen(100);
            entity.ReplyTo = StringHelper.EnsureNotNull(ReplyTo).TrimToLen(100);
            entity.Subject = StringHelper.EnsureNotNull(Subject).TrimToLen(100);
            entity.Text = StringHelper.EnsureNotNull(Text);
            entity.SendAttemptCount = SendAttemptCount;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the message state.
    /// </summary>
    public class SetMessageState : UpdateAuditable<MessageEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the number of send attempts.
        /// </summary>
        public int SendAttemptCount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sending date.
        /// </summary>
        public DateTime? SentOn
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public MessageFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Revise(IUnitOfWork uow, MessageEntity entity)
        {
            entity.SendAttemptCount = SendAttemptCount;
            entity.SentOn = SentOn;
            entity.Flags = Flags;

            base.Revise(uow, entity);
        }

        #endregion
    }

    /// <summary>
    /// Deletes the message.
    /// </summary>
    public class DeleteMessage : DeleteEntity<MessageEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("MSG");
            }
        }

        #endregion
    }

    /// <summary>
    /// Gets the message by identifier.
    /// </summary>
    public class GetMessage : GetEntity<MessageEntity>,
        IApiOperation
    {
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over message collection.
    /// </summary>
    public class SearchMessages : SearchOperation<MessageEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchMessages()
        {
            Term = SearchTerm.Empty;
            From = SearchTerm.Empty;
            To = SearchTerm.Empty;
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                MessageSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sender identifier.
        /// </summary>
        public Guid? StorageID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sender address.
        /// </summary>
        public SearchTerm From
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recepient address.
        /// </summary>
        public SearchTerm To
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the send attempt count "from".
        /// </summary>
        public int? SendAttemptCountFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the send attempt count "to".
        /// </summary>
        public int? SendAttemptCountTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public MessageFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date 'from'.
        /// </summary>
        public DateTime? CreatedFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date 'to'.
        /// </summary>
        public DateTime? CreatedTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sending date 'from'.
        /// </summary>
        public DateTime? SentFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sending date 'to'.
        /// </summary>
        public DateTime? SentTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public MessageSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<MessageEntity> Build(IUnitOfWork uow, IQueryable<MessageEntity> query)
        {
            query = base.Build(uow, query);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            query = query.FilterByDateRange(x => x.CreatedOn, CreatedFrom, CreatedTo);
            query = query.FilterByNumRange(x => x.SendAttemptCount, SendAttemptCountFrom, SendAttemptCountTo);
            if(SentFrom.HasValue || SentTo.HasValue)
            {
                query = query.Where(x => x.SentOn.HasValue);
                query = query.FilterByDateRange(x => x.SentOn.Value, SentFrom, SentTo);
            }
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.Subject.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.Subject.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.Subject.Contains(Term.Keyword) ||
                        x.Text.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.Subject.Equals(Term.Keyword));
                    break;
            }
            switch(From.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.From.StartsWith(From.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.From.EndsWith(From.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.From.Contains(From.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.From.Equals(From.Keyword));
                    break;
            }
            switch(To.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.To.StartsWith(To.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.To.EndsWith(To.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.To.Contains(To.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.To.Equals(To.Keyword));
                    break;
            }
            if(StorageID.HasValue)
                query = query.Where(x => x.SenderID == StorageID.Value);
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case MessageSortBy.From:
                            query = query.OrderBy(x => x.From, SortDir, i != 0);
                            break;
                        case MessageSortBy.To:
                            query = query.OrderBy(x => x.To, SortDir, i != 0);
                            break;
                        case MessageSortBy.SentOn:
                            query = query.OrderBy(x => x.SentOn, SortDir, i != 0);
                            break;
                        case MessageSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    #endregion

    #region __Bulk__

    /// <summary>
    /// Bulk deletes messages.
    /// </summary>
    public class DeleteMessages : Operation<Int32>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the creation date "from".
        /// </summary>
        public DateTime? CreatedFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date "to".
        /// </summary>
        public DateTime? CreatedTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sending date 'from'.
        /// </summary>
        public DateTime? SentFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sending date 'to'.
        /// </summary>
        public DateTime? SentTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the send attempt count "from".
        /// </summary>
        public int? SendAttemptCountFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the send attempt count "to".
        /// </summary>
        public int? SendAttemptCountTo
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out int result)
        {
            result = 0;
            foreach(var i in uow.Execute(new SearchMessages
            {
                CreatedFrom = CreatedFrom,
                CreatedTo = CreatedTo,
                SentFrom = SentFrom,
                SentTo = SentTo,
                SendAttemptCountFrom = SendAttemptCountFrom,
                SendAttemptCountTo = SendAttemptCountTo
            }).Result)
            {
                uow.Execute(new DeleteMessage
                {
                    EntityID = i.EntityID
                });
                result++;
            }
        }

        #endregion
    }

    #endregion

    #region __Misc__

    /// <summary>
    /// Creates the message from template.
    /// </summary>
    public class CreateTemplatedMessage : Operation<MessageEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the template identifier.
        /// </summary>
        public int TemplateID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the language identifier.
        /// </summary>
        public int LanguageID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sender address.
        /// </summary>
        public string From
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient address.
        /// </summary>
        public string To
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the BCC recipient address.
        /// </summary>
        public string BccTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the address where to reply to.
        /// </summary>
        public string ReplyTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the replacement dictionary.
        /// </summary>
        public IDictionary<String, String> Replacements
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public MessageFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            result = null;

            var template = uow.Execute(new GetMessageTemplate
            {
                ThrowsException = true,
                EntityID = TemplateID
            }).Result;
            if(!template.Flags.HasFlag(MessageTemplateFlags.Active))
                return;

            var translation = template.Translation(LanguageID);
            if(translation == null)
                return;

            string from = String.IsNullOrWhiteSpace(From) ? template.From : From;
            string to = String.IsNullOrWhiteSpace(To) ? template.To : To;
            string bccTo = String.IsNullOrWhiteSpace(BccTo) ? template.BccTo : BccTo;
            string replyTo = String.IsNullOrWhiteSpace(ReplyTo) ? template.ReplyTo : ReplyTo;
            string subject = translation.Subject;
            string text = translation.Text;

            if(String.IsNullOrWhiteSpace(replyTo))
                replyTo = from;
            foreach(var kv in Replacements)
            {
                string token = String.Format("[T:{0}]", kv.Key);

                subject = subject.Replace(token, kv.Value);
                text = text.Replace(token, kv.Value);
            }

            subject = Regex.Replace(subject, @"\[T\:[\w.]*\]", String.Empty, RegexOptions.IgnoreCase);
            text = Regex.Replace(text, @"\[T\:[\w.]*\]", String.Empty, RegexOptions.IgnoreCase);

            result = uow.Execute(new CreateMessage
            {
                SenderID = template.SenderID,
                From = from,
                To = to,
                BccTo = bccTo,
                ReplyTo = replyTo,
                Subject = subject,
                Text = text,
                SendAttemptCount = 0,
                Flags = Flags
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Creates the message from template using template code.
    /// </summary>
    public class CreateTemplatedMessageByCode : Operation<MessageEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the template code.
        /// </summary>
        public string TemplateCode
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the language identifier.
        /// </summary>
        public int LanguageID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sender address.
        /// </summary>
        public string From
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient address.
        /// </summary>
        public string To
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the BCC recipient address.
        /// </summary>
        public string BccTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the address where to reply to.
        /// </summary>
        public string ReplyTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the replacement dictionary.
        /// </summary>
        public IDictionary<String, String> Replacements
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public MessageFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            result = uow.Execute(new CreateTemplatedMessage
            {
                TemplateID = uow.Execute(new GetMessageTemplateByCode
                {
                    ThrowsException = true,
                    Code = TemplateCode
                }).Result.EntityID,
                LanguageID = LanguageID,
                From = From,
                To = To,
                BccTo = BccTo,
                ReplyTo = ReplyTo,
                Replacements = Replacements,
                Flags = Flags
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Sends the message.
    /// </summary>
    public class SendMessage : OperateEntityByID<MessageEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("MSG");
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            var message = uow.Execute(new GetMessage
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                var sender = ioc.Resolve(message.SenderID)
                    as IMessageSender;
                int sendRet = sender.Send(message);

                message = uow.Execute(new SetMessageState
                {
                    EntityID = EntityID,
                    SendAttemptCount = message.SendAttemptCount + 1,
                    SentOn = sendRet > 0
                        ? DateTime.UtcNow
                        : (DateTime?)null,
                    Flags = sendRet != 0
                        ? (message.Flags & (~MessageFlags.Active))
                        : message.Flags
                }).Result;
                if(sendRet < 0)
                {
                    uow.Execute(new CreateLogRecord
                    {
                        Source = sender.GetType().Name,
                        Description = Err.Of<MessageEntity>().Code(message.EntityID).NotPossible("Send"),
                        Text = Err.Of(sender).Code(sendRet),
                        Flags = LogRecordFlags.Warning | LogRecordFlags.Medium
                    });
                }
            }
            result = message;
        }

        #endregion
    }

    #endregion
}
